/**
 * @file   ModSimio.java
 * @author Omar Quintana <o.quintana@lpcsite.com> y Enrique Madrid <e.madrid@lpcsite.com>
 * @date   Sun Sep  2 23:38:43 2007
 *
 * @brief
 *
 *
 */

package sim.framework.io;

import java.io.*;
import javax.microedition.io.*;

import sim.framework.*;
import sim.framework.dialog.*;
import sim.utils.*;
import sim.pref.DocPref;
import sim.utils.*;

public abstract class ModSimio extends Module {

    // Estado del protocolo
    public static final int STATUS_NONE = 0;
    public static final int STATUS_COMMAND = 1;
    public static final int STATUS_RUNNING = 2;
    public static final int STATUS_INPUT = 3;
    public static final int STATUS_READY = 4;

    // Estado de la conexión
    private static final int STATE_CONNECTING = Executable.STATE_USER + 1;
    private static final int STATE_SENDING = Executable.STATE_USER + 2;
    private static final int STATE_RECEVING = Executable.STATE_USER + 3;
    private static final int STATE_PROTOCOL = Executable.STATE_USER + 4;
    private static final int STATE_DISCONNECTING = Executable.STATE_USER + 5;
    
    // Dimensiones de los buffers.
    private static final int BUFFER_INP_MINLEN = 1024;
    private static final int BUFFER_OUT_MINLEN = 256;
        
    // Valores retornados por callbacks
    protected static final int OK = 0;
    protected static final int CANCEL  = 1;
    
    // Protocolo
    private static final int PROMPT_LENGTH = 8;
    
    // Comunicaciones
    private Socket m_Socket;  // Objeto Socket para las comunicaciones
    private int m_Tx = 0;     // Cantidad de bytes transmitidos
    private int m_Rx = 0;     // Cantidad de bytes recibidos
    
    // Buffers
    protected Buffer m_BufInp = new Buffer(BUFFER_INP_MINLEN); // Cache de datos leidos.
    protected Buffer m_BufOut = new Buffer(BUFFER_OUT_MINLEN); // Cadhe de datos enviados.
    
    // Protocolo
    private byte[] m_Prompt = new byte[PROMPT_LENGTH];
    private String m_Command = null;
    private int m_Status = STATUS_NONE;
    private boolean m_Ready = false;
    
    // Interfaz gráfica.
    private DlgNotify m_Dlg = null;
    private boolean m_Cancel = false;
    
    /**
     * Constructor.
     *
     * @param i Identificador el módulo.
     * @param s Referencia al Simlet.
     *
     * @return Devuelve una instancia de la clase ModSimio.
     */
    public ModSimio(int i, Simlet s){
        super(i, s);
    }

    /**
     * Imprime un mensaje en la interfaz gráfica.
     *
     * @param message
     */
    protected final void printMessage(final String message) {
        if (m_Dlg != null) {
            m_Dlg.setText(m_Dlg.CTRL_MAIN, message);
        }
    }
    
    /**
     * Imprime los bytes transmitidos y enviados por la conexión.
     *
     */
    private final void printLog() {
        if (m_Command != null) {
            printMessage(m_Command + "\n" +
			 "Bytes Tx: " + String.valueOf(m_Tx) + "\n" +
			 "Bytes Rx: " + String.valueOf(m_Rx)+ "\n" +
			 "Buff. Tx: " + m_BufOut.count() + "\n" +
			 "Buff. Rx: " + m_BufInp.count());
        }
    }
    
    /**
     * Devuelve el estado del protocolo.
     *
     *
     * @return Devuelve un valor entero equivalente a las constantes.
     *         STATUS_COMMAND, STATUS_RUNNING, STATUS_INPUT, STATUS_READY
     */
    protected final int getStatus() {
        return m_Status;
    }
    
    protected final boolean isConnected() {
        if (m_Socket != null) {
            return m_Socket.opened();
        }
        return false;
    }
    
    public void work(final Msg msg){
                
        // Mientras no se cancela ...
        if (!m_Cancel) {
            // hacemos trabajo en segundo plano.
            switch(msg.getWParam()) {
	    case Executable.STATE_INIT:
		workInit(msg);
		break;
	    case ModSimio.STATE_CONNECTING:
		workConnecting(msg);
		break;
	    case ModSimio.STATE_RECEVING:
		workReceiving(msg);
		break;
	    case ModSimio.STATE_SENDING:
		workSending(msg);
		break;
	    case ModSimio.STATE_PROTOCOL:
		workProtocol(msg);
		break;
            }
        } else {
            //#ifdef DEBUG_MODSIMIO
	    //# System.out.println("\tModSimio cancelado!!!");
            //#endif
            closeConnection();
            simioCancel();
        }
        
    }
    
    private void notifyProtocol(int status) {
	m_Status = status;
	todo(STATE_PROTOCOL);        
    }
    
    public boolean event(Msg msg) {
        boolean ret = false;
        
        //#ifdef DEBUG_MODSIMIO
	//# System.out.println("ModSimio::event() ENTERED");
        //#endif
        
        switch(msg.getEventCode())   {
	case EventListener.COMMAND:
	    switch (msg.getWParam()) {
	    case Dialog.CTRL_BUTTON_BACK:
		//#ifdef DEBUG_MODSIMIO
		//# System.out.println("\tUsurio presiona CANCEL!!!");
		//#endif
		m_Cancel = true;
		if (!isConnected()) {
		    //#ifdef DEBUG_MODSIMIO
		    //# System.out.println("\tLlamar al protocolo ...");
		    //#endif
		    todo(STATE_PROTOCOL);
		}
		ret = true;
	    }
        }
        
        //#ifdef DEBUG_MODSIMIO
	//# System.out.println("ModSimio::event() EXITING");
        //#endif
        
        return ret;
    }
    
    private void workInit(final Msg msg) {
        
        //#ifdef DEBUG_MODSIMIO
	//# System.out.println("ModSimio::workInit() ENTERED");
        //#endif
        
        m_Dlg = (DlgWarning)DlgWarning.create(m_Simlet,
					      getLabel(Resource.LBL_TITLE_APP),
					      null,
					      null,
					      getLabel(Resource.LBL_CTRL_CANCEL));
        setGui(m_Dlg);
        
        todo(STATE_CONNECTING);
        
        //#ifdef DEBUG_MODSIMIO
	//# System.out.println("ModSimio::workInit() EXITING");
        //#endif
    }
    
    private void workConnecting(final Msg msg) {
        DocPref doc;
        String message;
        
        //#ifdef DEBUG_MODSIMIO
	//# System.out.println("ModSimio::workConnecting() ENTERED");
        //#endif
        
        doc = (DocPref)getDocument(Simlet.MOD_PREF);
        // Por siacaso el documento no este cargado
        if (doc == null) {
            doc = new DocPref(m_Simlet);
            if (doc.load()) {
                setDocument(Simlet.MOD_PREF, doc);
            }
        }
        try {
            // Creando una conexión
            printMessage(getLabel(Resource.LBL_MESG_SYNC_CONNETING));
            m_Socket = (Socket)Socket.create(m_Simlet, doc.getAddress(), doc.getPort());
        } catch (ConnectionNotFoundException cnfe) {
            //#ifdef DEBUG_MODSIMIO
	    //# System.out.println("\tModSimio::workConnecting() catch ConnectionNotFoundException");
            //#endif
            closeConnection();
            // Servidor no disponible.
            printMessage(getLabel(Resource.LBL_MESG_SYNC_SERVER_ERR));
        } catch (IllegalArgumentException iae) {
            //#ifdef DEBUG_MODSIMIO
	    //# System.out.println("\tModSimio::workConnecting() catch IllegalArgumentException");
            //#endif
            closeConnection();
            // Error de parametros.
            message = getLabel(Resource.LBL_MESG_SYNC_CONNETING) + doc.getAddress() + doc.getPort();
            printMessage(message);
        } catch (IOException ioe) {
            //#ifdef DEBUG_MODSIMIO
	    //# System.out.println("\tModSimio::workConnecting() catch IOException");
            //#endif
            closeConnection();
            // Red no disponible.
            printMessage(getLabel(Resource.LBL_MESG_SYNC_NETWORK_ERR));
        } finally {
            if (isConnected()) {
                // Debemos recibir el prompt
                todo(STATE_RECEVING);
            }
        }

        //#ifdef DEBUG_MODSIMIO
	//# System.out.println("ModSimio::workConnecting() EXITING");
        //#endif
        
    }
    
    private void workReceiving(final Msg msg) {
        int ret;
        
        //#ifdef DEBUG_MODSIMIO
	//# System.out.println("ModSimio::workReceiving() ENTERED");
        //#endif
        
        try {
            // Leyendo datos de la red
            ret = m_Socket.read(m_BufInp);
            if (ret > 0) {
                // Usar el contenido del buffer según protocolo
                switch (getStatus()) {
		case STATUS_NONE:
		    // Si el buffer contiene la longitud del prompt ...
		    if (m_BufInp.length() == PROMPT_LENGTH) {
			// ... encontramos el prompt.
			Utils.memcpy(m_Prompt, 0,
				     m_BufInp.bytes(), m_BufInp.offset(),
				     PROMPT_LENGTH);
			m_BufInp.consume(PROMPT_LENGTH);
			// Es hora de enviar un commando.
			notifyProtocol(STATUS_COMMAND);
		    } else {
			// ... Esperarar que llegue el prompt.
			todo(STATE_RECEVING);
		    }
		    break;
		default:
		    if (!m_Ready) {
			// Detectar el prompt
			detectPromptPart();
		    }
		    // Notificar al cliente
		    notifyProtocol(STATUS_INPUT);
                }
                m_Rx += ret;
                printLog();
            }
	    else {
		// Desconectado
		printMessage(getLabel(Resource.LBL_MESG_SYNC_SOCKET_ERR));
		notifyProtocol(STATUS_NONE);
	    }
        } catch (IOException ioe) {
            printMessage(getLabel(Resource.LBL_MESG_SYNC_SOCKET_ERR));
	    notifyProtocol(STATUS_NONE);
        }
        
        //#ifdef DEBUG_MODSIMIO
	//# System.out.println("ModSimio::workReceiving() EXITING");
        //#endif
        
    }
    
    private void workSending(final Msg msg) {
        //#ifdef DEBUG_MODSIMIO
	//# System.out.println("ModSimio::workSending() ENTERED");
        //#endif
        try {
            switch (getStatus()) {
	    case STATUS_COMMAND:
		if (m_BufOut.length() > 0) {
		    m_Tx += m_Socket.write(m_BufOut);
		    printLog();
		    m_BufOut.clear();
		    notifyProtocol(STATUS_RUNNING);
		} else {
		    notifyProtocol(STATUS_NONE);
		}
		break;
	    case STATUS_RUNNING:
		if (m_BufOut.length() > 0) {
		    m_Tx += m_Socket.write(m_BufOut);
		    printLog();
		    m_BufOut.clear();
		    notifyProtocol(STATUS_RUNNING);
		} else {
		    todo(STATE_RECEVING);
		}
		break;
            }
        } catch (IOException ioe) {
            closeConnection();
            printMessage(getLabel(Resource.LBL_MESG_SYNC_SOCKET_ERR));
        }
        //#ifdef DEBUG_MODSIMIO
	//# System.out.println("ModSimio::workSending() EXITING");
        //#endif
    }
    
    private void workProtocol(final Msg msg) {

        switch (getStatus()) {
	case STATUS_NONE:
	    workProtocolNone(msg);
	    break;
	case STATUS_COMMAND:
	    workProtocolCommand(msg);
	    break;
	case STATUS_RUNNING:
	    workProtocolRunning(msg);
	    break;
	case STATUS_INPUT:
	    workProtocolInput(msg);
	    break;
	case STATUS_READY:
	    workProtocolReady(msg);
	    break;
        }        

    }

    private void workProtocolNone(final Msg msg) {
        //#ifdef DEBUG_MODSIMIO
	//# System.out.println("ModSimio::workProtocolNone() ENTERED");
        //#endif

	closeConnection();
	printMessage("Protocol error!!!");
	simioClient();	
	
        //#ifdef DEBUG_MODSIMIO
	//# System.out.println("ModSimio::workProtocolNone() EXITING");
        //#endif
    }

    private void workProtocolCommand(final Msg msg) {
        //#ifdef DEBUG_MODSIMIO
	//# System.out.println("ModSimio::workProtocolCommand() ENTERED");
        //#endif

	if (simioClient() == OK) {
	    todo(STATE_SENDING);
	} else {
	    notifyProtocol(STATUS_NONE);
	}
	
        //#ifdef DEBUG_MODSIMIO
	//# System.out.println("ModSimio::workProtocolCommand() EXITING");
        //#endif
    }

    private void workProtocolRunning(final Msg msg) {
        //#ifdef DEBUG_MODSIMIO
	//# System.out.println("ModSimio::workProtocolRunning() ENTERED");
        //#endif
	
	if (simioClient() == OK) {
	    todo(STATE_SENDING);
	} else {
	    todo(STATE_RECEVING);
	}
	
        //#ifdef DEBUG_MODSIMIO
	//# System.out.println("ModSimio::workProtocolRunning() EXITING");
        //#endif
    }

    private void workProtocolInput(final Msg msg) {
        //#ifdef DEBUG_MODSIMIO
	//# System.out.println("ModSimio::workProtocolInput() ENTERED");
        //#endif

	if (simioClient() == OK) {
	    if (!m_Ready) {
		//#ifdef DEBUG_MODSIMIO
		//# System.out.println("\tFalta recibir datos ...");
		//#endif
		todo(STATE_RECEVING);
	    } else {
		//#ifdef DEBUG_MODSIMIO
		//# System.out.println("\tCommando terminado!");
		//#endif
		notifyProtocol(STATUS_READY);
	    }
	} else {
	    closeConnection();
	    notifyProtocol(STATUS_NONE);
	}

        //#ifdef DEBUG_MODSIMIO
	//# System.out.println("ModSimio::workProtocolInput() EXITING");
        //#endif
    }

    private void workProtocolReady(final Msg msg) {
        //#ifdef DEBUG_MODSIMIO
	//# System.out.println("ModSimio::workProtocolReady() ENTERED");
        //#endif
	
	m_BufInp.clear();
	m_BufOut.clear();
	simioClient();
	
        //#ifdef DEBUG_MODSIMIO
	//# System.out.println("ModSimio::workProtocolReady() EXITING");
        //#endif
    }
    
    protected final void read() {
        int length;
        int ret;

        //#ifdef DEBUG_MODSIMIO
	//# System.out.println("ModSimio::read() ENTERED");
        //#endif

	// Longitud disponible para leer
	length = 0;
        if (m_BufInp.length() > 0) {
	    // Si ya se encontro el prompt ...
            if (m_Ready){
		// ... todo lo que haya en el socket.
                length = m_BufInp.length();
		// ... en caso contrario, Si el buffer de entrada
		// contiene más cantidad de bytes que el prompt ...
            } else if (m_BufInp.length() > PROMPT_LENGTH) {
		// No leer la última parte que pude contener parte del prompt.
                length = m_BufInp.length() - PROMPT_LENGTH;
            }
	    // Si la longitud disponible para leer es mayor que cero.
            if (length > 0) {
                ret = simioReadData(m_BufInp.bytes(), m_BufInp.offset(), length);
		m_BufInp.consume(ret);
		//#ifdef DEBUG_MODSIMIO
		//# if (length > ret) {
		//# System.out.println("\tModSimio::simioReadData() no ha utilizado todos los bytes disponibles!!!");
		//# System.out.println("\tBytes disponibles para lectura: " + length);
		//# System.out.println("\tBytes utilizados por simioReadData(): " + ret);
		//# }
		//#endif                
            }
        }

        //#ifdef DEBUG_MODSIMIO
	//# System.out.println("ModSimio::read() EXITING");
        //#endif
    }
    
    protected final void gets() {
        int ret = 0;
        if (m_BufInp.length() > 0) {
            do {
                ret = Utils.memmem(m_BufInp.bytes(), m_BufInp.offset(), m_BufInp.length(), Utils.ENDOFLINE);
                ret = ((ret == -1) && (m_Ready)) ? (m_BufInp.offset() + m_BufInp.length()) : ret;
                if (ret > -1) {
                    if (!simioGetsLine(m_BufInp.bytes(), m_BufInp.offset(), ret - m_BufInp.offset())) {
                        break;
                    }
                    m_BufInp.consume(ret - m_BufInp.offset());
                    m_BufInp.consume(Utils.ENDOFLINE.length);
                }
            } while ((ret >= 0) && (m_BufInp.length() > 0));
        }
    }
    
    protected final int read(byte[] data, int start, int count) {
        int ret = 0;
        if (m_BufInp.length() > 0) {
            if (m_Ready){
                ret = m_BufInp.length();
            } else if (m_BufInp.length() > PROMPT_LENGTH) {
                ret = m_BufInp.length() - PROMPT_LENGTH;
            }
            ret = Math.min(count, ret);
            Utils.memcpy(data, start,
			 m_BufInp.bytes(), m_BufInp.offset(),
			 ret);
            m_BufInp.consume(ret);
        }
        return ret;
    }
    
    protected final int read(byte[] data, int start) {
        return read(data, start, data.length - start);
    }
    
    protected final int read(byte[] data) {
        return read(data, 0, data.length);
    }
    
    protected final int write(final byte[] data, int start, int count) {
        int ret = 0;
        int len = 0;
        
        try {
            switch (getStatus()) {
	    case STATUS_COMMAND:
		m_BufOut.append(data, start, count);
		detectCommandPart();
		ret = count;
		break;
	    case STATUS_RUNNING:
		do {
		    if (m_BufOut.count() == m_BufOut.length()) {
			m_Tx += m_Socket.write(m_BufOut);
			printLog();
			m_BufOut.clear();
		    }
		    len = Math.min(m_BufOut.count() - m_BufOut.length(), count);
		    m_BufOut.append(data, start, len);
		    ret += len;
		} while (ret < count);
		break;
            }
            printLog();
        } catch (IOException ioe) {
            printMessage(getLabel(Resource.LBL_MESG_SYNC_SOCKET_ERR));
            closeConnection();
        }
        
        return ret;
    }
    
    protected final int write(final byte[] data, int start) {
        return write(data, start, data.length - start);
    }
    
    protected final int write(final byte[] data) {
        return write(data, 0, data.length);
    }
    
    private void detectPromptPart() {
	//#ifdef DEBUG_MODSIMIO
	//# System.out.println("ModSimio::detectPromptPart() ENTERED");
	//#endif
	
        if (m_BufInp.length() >= PROMPT_LENGTH) {
            if (Utils.memncmp(m_BufInp.bytes(), m_BufInp.length() - PROMPT_LENGTH,
			      m_Prompt, 0,
			      PROMPT_LENGTH) == 0) {
                m_BufInp.consumeEnd(PROMPT_LENGTH);
		//#ifdef DEBUG_MODSIMIO
		//# System.out.println("\n");
		//# System.out.println("\tPROMPT ENCONTRADO!!!");
		//# System.out.println("\n");
		//#endif
                m_Ready = true;
            }
        }
	
	//#ifdef DEBUG_MODSIMIO
	//# System.out.println("ModSimio::detectPromptPart() EXITING");
	//#endif
    }
    
    private void detectCommandPart() {
        int tok;
        if (m_Command == null) {
            tok = Utils.memmem(m_BufOut.bytes(), m_BufOut.offset(), m_BufOut.length(),
			       Utils.TOKEN, Utils.TOKEN_SPACE, 1);
            if (tok > -1) {
                m_Command = new String(m_BufOut.bytes(),
				       m_BufOut.offset(),
				       tok - m_BufOut.offset());
            }
        }
    }
    
    protected final void sendCommand() {
        if (getStatus() == STATUS_READY) {
            m_Command = null;
            m_Ready = false;
            m_Status = STATUS_COMMAND;
            todo(STATE_PROTOCOL);
        }
    }
    
    protected final void closeConnection() {
        // Cerrar el socket
        if (m_Socket != null) {
            m_Socket.close();
        }
        m_BufInp.clear();
        m_BufOut.clear();
        m_Command = null;
        m_Rx = 0;
        m_Tx = 0;        
        m_Ready = false;
        m_Status = STATUS_NONE;
    }
    
    protected final void execBack() {
        //#ifdef DEBUG_MODSIMIO
	//# System.out.println("ModSimio::execBack() ENTERED");
        //#endif
        
        // Redefinimos por si un descuidado llama a execBack()
        // antes de cerrar la connexión.
        closeConnection();
        super.execBack();
        
        //#ifdef DEBUG_MODSIMIO
	//# System.out.println("ModSimio::execBack() EXITING");
        //#endif
        
    }
    
    protected final void execNext() {
        //#ifdef DEBUG_MODSIMIO
	//# System.out.println("ModSimio::execNext() ENTERED");
        //#endif
        
        // Redefinimos por si un descuidado llama a execNext()
        // antes de cerrar la connexión.
        closeConnection();
        super.execNext();
        
        //#ifdef DEBUG_MODSIMIO
	//# System.out.println("ModSimio::execNext() EXITING");
        //#endif
    }
    
    /**
     * Implementacion de la derivada.
     *
     */
    
    protected abstract int simioClient();
    
    protected abstract int simioCancel();
    
    protected int simioReadData(final byte[] line, int start, int length) {
        return 0;
    }
    
    protected boolean simioGetsLine(final byte[] line, int start, int length) {
        return false;
    }
    
}
